\documentclass[a4paper]{article}

\usepackage{listings}
\usepackage{color}
\usepackage{polski}
\usepackage[utf8]{inputenc}
\usepackage{color}
\usepackage{graphicx}
\usepackage{listings}
\usepackage{float}

\setcounter{secnumdepth}{5}

\definecolor{bluekeywords}{rgb}{0.13,0.13,1}
\definecolor{greencomments}{rgb}{0,0.5,0}
\definecolor{redstrings}{rgb}{0.9,0,0}

\lstset{language=[Sharp]C,
showspaces=false,
showtabs=false,
breaklines=true,
showstringspaces=false,
breakatwhitespace=true,
escapeinside={(*@}{@*)},
commentstyle=\color{greencomments},
keywordstyle=\color{bluekeywords}\bfseries,
stringstyle=\color{redstrings},
basicstyle=\ttfamily,
xleftmargin=23pt
}


\title{Wirtualna Myszka}

\author{Kamil Pajdzik (numer indeksu 100596)
	 \\ Adam Popiołek (numer indeksu 99398)}

\begin{document}
\maketitle

\section{Wstęp}

\subsection{Cel projektu}

Celem projektu Wirtualna Myszka było stworzenie oprogramowania, które umożliwia obsługę kursora myszy bez używania myszy komputerowej, a z wykorzystaniem kamery cyfrowej.

\subsection{Oprogramowanie}
Projekt został stworzony w języku programowania C\# w wersji 4.0 oraz środowisku uruchomieniowym .NET w wersji 4.0. Wykorzystanym środowiskiem programistycznym był program Microsoft Visual Studio 2010. Biblioteką pomocniczą było OpenCV wraz z wrapperem dla języków rodziny .NET EmguCV. Dodatkowym oprogramowaniem, które zostało wykorzystane jest system kontroli wersji SVN hostowany przez serwis code.google.com.

\subsection{Hardware}
Projekt był testowany na dwóch komputerach. Pierwszy z nich pracuje na czterordzeniowym procesorze Intel i5 2420M taktowanym zegarem 2,4 GHz, czterech gigabajtach pamięci RAM, karcie graficznej ATI Radeon HD 6630M oraz 2 Mpx kamerze. Drugi posiada procesor Intel i3 330M w wraz z 3 GB pamięci RAM, układ graficzny ATI Mobility Radeon HD 5650 oraz camerą o rozdzielczości 640 na 480 pikseli. Na obu jednostkach projekt działał płynnie i poprawnie, nie utrudniając pozostałych czynności.

\section{Algorytm}

\begin{figure}[H]
\centering
\includegraphics[width=\linewidth]{img/schemat_blokowy.png}
\caption{Schemat blokowy programu}
\label{fig:awesome_image}
\end{figure}

\subsection{Inicjalizacja}
Program zaczyna swoje działanie w klasie \texttt{Program}. Następnie tworzona jest instancja klasy \texttt{CameraController}, która w konstruktorze dokonuje niezbędnej inicjalizacji. 

\begin{lstlisting}[caption=Kod źródłowy konstruktora klasy \texttt{CameraController}, numbers=left]
capture = new Capture(0);
viewer = new ImageViewer();
mouse = new MouseSimulator();
Application.Idle += new EventHandler(FrameHandle);
this.viewer.Disposed += ((sender, args) => Application.Exit());
this.viewer.KeyDown += ViewerOnKeyDown;
this.viewer.KeyPreview = true;
\end{lstlisting}

Kolejno tworzony jest obiekt, który pozwala na pobieranie klatek z kamery, okno do wyświetlania pobranych klatek oraz klasa symulująca zachowanie myszki. Następnie dodawany jest delegat funkcji \texttt{FrameHandle} do właściwości \texttt{Idle}. Powoduje to wywołanie funkcji \texttt{FrameHandle} za każdym razem, gdy program ma zamiar przejść do stanu bezczynności. Program \textit{Wirtualna Myszka} nie przeprowadza żadnych innych działań niż wcześniej wspomniana funkcja więc będzie ona wywoływana cały czas. Ostatnim działaniem jest dodanie i odblokowanie akcji \texttt{KeyDown}, która w naszym przypadku obsługuje tylko klawisz escape i po wciśnięciu go, zwalnia zasoby i zamyka program.

Projekt po uruchomieniu wykorzystuje tylko jedno okno, w którym wyświetlany jest widok z kamery wraz z obrysem dłoni oraz informacją o obliczonym środku dłoni oraz liczbie wykrytych palców. W przypadku, gdy dłoń nie zostanie wykryta, oprogramowanie zasygnalizuje to czarnym widokiem z komunikatem w oknie. Oprócz konturu dłoni wyświetlana jest również otoczka wypukła dłoni oraz punkt centralny dłoni.
Ponieważ projekt został przewidziany jako zastępstwo dla myszki, nie posiada on żadnych elementów interaktywnych jak na przykład suwaków do kalibracji.

\begin{figure}[H]
\centering
\includegraphics[width=\linewidth]{img/inrange.png}
\caption{Klatka z kamery po wywołaniu funkcji \texttt{InRange}}
\end{figure}

\subsection{Wykrywanie skóry}

\subsubsection{Algorytm}

Wykrywanie dokonane było na obrazie źródłowym przekonwertowanym do przestrzeni kolorów YCbCr, a następnie wybraniu tylko tych pikseli, które należą do danego funkcji zakresu. W modelu tym składowa Y odpowiada wartości luminancji, Cb różnicę pomiędzy wartością luminacji, a kolorem niebieskim, a Cb różnicy pomiędzy luminancją, a czerwienią. Dzięki wykorzystaniu modelu YCbCr, mogliśmy wyeliminować problem naświetlenia i skupić się jedynie na poprawnym określeniu zakresu koloru.

Całość tej operacji przeprowadzaliśmy w funkcji \texttt{DetectSkin}, która jako argument pobiera obraz w przestrzeni kolorów RGB oraz dwa kolory wyznaczające kolejno dolną oraz górną granicę. Funkcja ta dodatkowo przeprowadza erozję oraz dylatację dla polepszenia zwróconego w wyniku obrazu. Jako parametry granic wybraliśmy następujące przedziały: Y = [0, 255] (aby wyeliminować problem naświetlenia), Cb = [131, 185], oraz Cr = [80, 135]. Wartości te zostały dobrane eksperymentalnie (oprócz Y) na podstawie sprzętu wykorzystanego do testowania. 

\begin{lstlisting}[caption=Kod źródłowy funkcji \texttt{DetectSkin}, numbers=left]
var currentYCrCbFrame = img.Convert<Ycc, Byte>();
var skin = new Image<Gray, byte>(img.Width, img.Height);
skin = currentYCrCbFrame.InRange((Ycc)min, (Ycc)max);
var rect_12 = new StructuringElementEx(12, 12, 6, 6, Emgu.CV.CvEnum.CV_ELEMENT_SHAPE.CV_SHAPE_RECT);
CvInvoke.cvErode(skin, skin, rect_12, 1);
var rect_6 = new StructuringElementEx(6, 6, 3, 3, Emgu.CV.CvEnum.CV_ELEMENT_SHAPE.CV_SHAPE_RECT);
CvInvoke.cvDilate(skin, skin, rect_6, 2);
return skin;
\end{lstlisting}

\subsubsection{Eksperymenty}
Przy tworzeniu projektu zostały przeprowadzone próby wykrywania skóry na innych profilach kolorów. Pierwszym eksperymentem było wykrywanie pożądanych elementów w profilu RGB. Niestety, trudnym zadaniem jest określenie przedziału kolorów, do którego należy skóra. Problem staje się jeszcze bardziej złożony, gdy weźmiemy pod uwagę zmieniające się warunki oświetleniowe. Pierwszym profilem pozbawionym tych wad, który nasuwa się na myśl jest profil HSV, gdzie poszczególne składowe odzwierciedlają kolejno odcień światła, nasycenie koloru i jasność koloru. Ignorując kanał V pozbywamy się problemu zmiennych warunków oświetleniowych. Niestety nie udało nam się dobrać na tyle uniwersalnych wartości pozostałych składowych, aby program działał w większości warunków. Najlepsze efekty funkcja \texttt{InRange} dawała dla przedziałów H = [0, 60], S = [0,23, 0,67] oraz V w całym swoim przedziale, ale nadal były to gorsze wyniki niż te, które zapewniała nam bliźniacza funkcja działająca na profilu YCbCr.

\subsubsection{Problemy}
Największym minusem tego podejścia jest wykrywanie oprócz skóry wszelkich powierzchni o kolorze do skóry podobnym. Dodatkowo podejście to nie ma zastosowania przy osobach innej rasy (czarnej, bądź żółtej).


\subsection{Wykrywanie dłoni}

\begin{figure}[H]
\centering
\includegraphics[width=\linewidth]{img/normal.png}
\caption{Klatka z kamery przy niezgiętych palcach. Kolorem brązowym zaznaczony jest wykryty obwód dłoni. Dodatkowo zaznaczone są wykryte defekty. Początek i koniec defektu zaznaczone są kolorem niebieskim oraz czerwonym, natomiast najgłębszy jego punk kolorem żółtym. Wszystkie 3 wierzchołki połączone są zielonymi odcinkami.}
\end{figure}

\subsubsection{Algorytm}
Wykrywanie dłoni polega na wyszukaniu wszystkich konturów na obrazie samej skóry wykrytej wcześniej przy pomocy \texttt{DetectSkin}, a następnie znalezieniu największego z nich. To czy dany kontur jest największy sprawdzane jest przy pomocy jego obwodu wydobytego z atrybutu \texttt{Perimiter}. Jest on następnie zwracany jako ręka. Wszystko to dzieje się w funkcji \texttt{DetectHand}, która jako arugment pobiera obraz skóry, natomiast zwraca kontur.


\subsubsection{Eksperymenty}
Początkowo do wyszukiwania konturu dłoni wykorzystywane było porównywanie pola wyznaczanego przez ten kontur, jednak zostało to zmienione na obwód, który daje zdecydowanie lepsze rezultaty. Dzieje się tak głównie dlatego, że dłoń ma stosunkowo duży obwód do pola, w odróżnieniu od np. głowy.

Drugim z eksperymentów przeprowadzonych w trakcie tworzenia projektu było wykorzystanie klasyfikatora kaskad Haara. Niestety nie udało nam się przygotować odpowiednio dobrego pliku XML z opisem otwartej dłoni, co powodowało więcej pomyłek przy wykrywanie dłoni niż podejście, które finalnie zastosowaliśmy. Dodatkowo, znacząco spadła wydajność programu, która oscylowała w okolicy pięciu klatek na sekundę. 

\subsubsection{Problemy}
Podstawową wadą algorytmu jest wykrywanie zbyt dużego konturu, gdy inne powierzchnie wykryte jako skóra połączą się z ręką. Zjawisko występuje szczególnie często, gdy na obrazie z kamery jakaś część naszej dłoni nachodzi na twarz lub na obiekt, o bardzo zbliżonym kolorze do skóry. Wykryty wtedy kontur obejmuje zarówno dłoń, jak i twarz. Dodatkowo zwraca on zły kontur obiektu, gdy ma on obwód większy niż dłoń, jednak jest to zjawisko rzadko występujące.


\subsection{Wykrywanie palców}

\subsubsection{Algorytm}
Zastosowane podejście opiera się na funkcji \texttt{GetConvexityDefacts}, wywołanej na konturze wykrytej przez nas dłoni. Funkcja ta zwraca defekty wypukłości (miejsca wklęsłe), takie jak np. przestrzeń między palcami. 
Następnie przy pomocy prostej funkcji warunkowej sprawdzane jest czy dana wklęsłość pasuje do wzoru przestrzeni między palcami. Aby tak było dwa górne wierzchołki wklęsłości (te na których dany defekt się zaczyna i kończy w konturze), muszą znajdować się wyżej o przynajmniej pięć pikseli niż trzeci wierzchołek defektu.

\subsubsection{Eksperymenty}
Eksperymenty w tym przypadku skupiły się głównie na przekazaniu do funkcji \texttt{GetConvexityDefacts} odpowiedniego przybliżenia konturu dłoni, tak aby wykrywanych defektów nie było zbyt dużo. Dodatkowo sprawdzone zostały różne metody wykrywania czy dany defekt jest przestrzenią między palcami. Rozważane było między innymi podejście ze sprawdzaniem czy położenia poszczególnych wierzchołków względem środka wykrytej dłoni, jednak jako że podstawowa metoda dawała zadowalające rezultaty, została ona ostatecznie zaimplementowana. 

\subsubsection{Problemy}
Przy implementowaniu projektu założyliśmy, że jedyną pozycją ręki, która będzie wykorzystywana będzie przy projekcie jest otwarta dłoń, ze skierowanymi palcami do góry. Przez to założenie projekt nie będzie wykrywał kliknięcia przy palcach skierowanych w inną stronę niż do góry. Nieistotnym problemem, o którym warto wspomnieć jest również niewykrywanie zbyt mocno odchylonego kciuka. Dodatkowo przy zastosowanej metodzie niemożliwym jest wykrycie jednego palca, gdyż kontur nie ma w tym przypadku wklęsłości, co jednak nie było przydatne w naszym projekcie.

\subsection{Symulacja myszki}
\begin{figure}[H]
\centering
\includegraphics[width=\linewidth]{img/click.png}
\caption{Klatka z kamery przy zgiętych palcach}
\end{figure}

\subsubsection{Algorytm}
Po wykonaniu wszystkich niezbędnych kroków związanych z wykryciem dłoni projekt wykonuje swoje główne zadanie czyli symuluje ruch myszy. W tym celu wykorzystujemy funkcje z interfejsu programistycznego Windows API. Wszystkie funkcje zostały zaimplementowane w klasie \texttt{MouseSimulator}. Podstawową funkcją jest \texttt{SendInput}, która symuluje wszystkie działania myszy i klawiatury. Funkcja ta wymaga przesyłania specyficznych struktur do niej więc dla uproszczenia symulacji zostały stworzone dodatkowe funkcje. Funkcja \texttt{Set\-Cursor\-Position\-(Point point)} ustawia kursor w danym punkcie, a \texttt{Move\-Cursor\-(int dx, int dy)} przesuwa kursor o podane wektory. Najważniejszą metodą jest \texttt{Move\-Cursor\-(Point p, Point windowSize)}, która przesuwa kursor, gdy środek ręki znajdzie się w odpowiedniej strefie. Obszary są wyliczane w zależności od rozmiaru okna i zajmują około jedną trzecią wyświetlanej klatki. W przypadku wprowadzenia środka dłoni w strefę po lewej stronie kursor przesunie się o dziesięć pikseli do góry. Funkcja zachowuje się analogicznie dla pozostałych krawędzi. Na przecięciu obszarów kursor porusza się na skos. 

Gdy algorytm wykryje zgięcie palców, projekt symuluje kliknięcie lewego przycisku w miejscu, gdzie znajduje się kursor. Wykorzystywana jest do tego funkcja \texttt{ClickLeftMouseButton()}.

\subsubsection{Eksperymenty}
Początkowym założeniem projektu było odwzorowanie naturalnego ruchu myszki tj. przeniesienie ruchu środka dłoni na ruch kursora. Niestety zbyt duże oscylacje kursora uniemożliwiały komfortową pracę. Próby ich wyeliminowania jak ograniczenie wektora, od którego kursor jest poruszany czy zmniejszenie częstotliwości pobierania klatek z kamery nie dawały wymiernych efektów.

\subsubsection{Problemy}
Po zmianie podejścia jedynymi problemami z jakimi się spotkaliśmy to wyeliminowanie błędnego wykrywania zgięcia palców oraz dobranie odpowiednich wartości wektora przesunięcia i rozmiaru stref. Rozwiązaniem pierwszej kwestii jest wywoływanie kliknięcia dopiero, gdy program wykryje trzy kolejne klatki ze zgiętymi palcami. Wartości przesunięcia wektora i rozmiaru stref dobraliśmy eksperymentalnie.

\section{Podsumowanie}
W trakcie tworzenia projektu spotkaliśmy się z wieloma problemami, ale zmieniając założenia czy podejście udało nam się je rozwiązać. Projekt wypełnia swoje zadanie symulacji ruchu i akcji myszką, choć trzeba pamiętać o narzuconych ograniczeniach. Projekt nie działa dla karnacji skóry innej niż kaukaska, a dłoń musi być skierowana do góry. Dodatkowo w kadrze nie może znajdować się obiekt w kolorze skóry, o obwodzie większym niż dłoń. 

\begin{thebibliography}{9}

\bibitem{emgucv}	\emph{http://www.emgu.com/wiki/files/2.4.2/document/Index.html} 	
\bibitem{opencv0}	\emph{http://opencv.willowgarage.com/documentation/cpp/index.html}	
\bibitem{opencv1}	S, Wojas \emph{Metody przetwarzania obrazów z wykorzystaniem biblioteki OpenCV}  
\bibitem{hsvskin}	V. A. Oliveira, A. Conci \emph{Skin Detection using HSV color space}.
\bibitem{handdet}	A. Mittal, A. Zisserman, P. H. S. Torr \emph{Hand detection using multiple proposals}

\end{thebibliography}

\end{document}

